<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>

    // 订阅发布器
    // 每个数据都有一个自己的订阅发布器
    // 他被定义在defineReactive方法里，存放在闭包中
    class Dep {
      constructor() {
        this.subs = []
      }

      depend() {
        this.subs.push(Dep.target)
      }
      notify() {
        this.subs.forEach(watcher => {
          watcher.update()        
        })
      }
    }

    // 观察者
    class Watcher {
      constructor(data, key) {
        this.value = data
        this.key = key

        Dep.target = this
        this.get()
        Dep.target = null
      }

      get() {
        return this.value[this.key]
      }
      // 观察者的响应动作，更新页面模板
      update() {
        console.log('监听' + this.key + '的watcher被触发了！新的值=' + this.get());
      }
    }



    function defineReactive(obj, key) {
      let value = obj[key]
      let dep = new Dep()
      observer(value) // 递归处理value数据

      Object.defineProperty(obj, key, {
        get: function () {
          console.log('正在访问:' + key);
          // 有一个观察者在访问这个数据，所以应该将这个观察者存入发布订阅中心

          if (Dep.target) {
            dep.depend()
          }
          return value
        },

        set(newValue) {
          console.log('正在修改:' + key);
          value = newValue
          observer(newValue)
          dep.notify()
        }
      })

    }

    class Observer {
      constructor(obj) {
        this.value = obj
        if (Array.isArray(obj)) {
          this.observerArray()
        } else {
          this.walk()
        }
      }

      observerArray() {
        for (let i = 0; i < this.value.length; i++) {
          observer(this.value[i])
        }
      }

      walk() {
        Object.keys(this.value).forEach(key => {
          defineReactive(this.value, key)
        })
      }

    }

    function observer(value) {
      // 如果value不是一个对象或者是null直接返回
      if (typeof value !== 'object' || value === null) return

      new Observer(value)
    }

    function Vue(options) {
      Object.keys(options).forEach(key => {
        this[key] = options[key]
      })

      observer(this.data)
      // console.log(this.data);

      new Watcher(this, 'data')
      new Watcher(this.data, 'count')
      new Watcher(this.data, 'user')
      new Watcher(this.data, 'list')
      new Watcher(this.data.user, 'name')
      new Watcher(this.data.user, 'desc')

    }

    let vm = new Vue({
      data: {
        count: 123,
        user: {
          name: 'zhangsan',
          desc: '法外狂徒'
        },
        list: ['YYDS', '双厨狂喜', 'AWSL']
      },
      test() {
        this.data.count++;
        this.data.user.name = 'lisi'
      }

    })

    vm.test()
  </script>
</body>

</html>